package Grantsfire::Geocoder;

use strict;
use warnings;
use threads;
use threads::shared;

use Geo::Coder::GoogleMaps;

sub addr_fields { qw/post_office_box street_address extended_address
 locality region country_name postal_code/ }

sub googlemaps_api_key { $_[1]->config->{googlemaps}->{devkey} }

sub cache_key { 
    my ($class, $vcard) = @_;
    return "geocode_" . $class->loc_string($vcard);
}

sub can_geocode {
    my ($class, $vcard) = @_;
    return $class->loc_string($vcard);
}

sub loc_string {
    my ($class, $vcard) = @_;

    # map addr fields, combine all address fields from the vcard into one array
    my @addr_fields = grep { $_ } map { my $l = "adr_$_"; $vcard->$l } $class->addr_fields;

    # strip out parenthetical expressions
    my @addr_fields_stripped;
    foreach my $addr_field (@addr_fields) {
        $addr_field =~ s/(\([^)]*\))//gs;
        push @addr_fields_stripped, $addr_field;
    }

    return join ', ', @addr_fields_stripped;
}

# given a set of vcards, return a hashref containing { $vcardid => { lat => x, lon => y } }
sub geocode_vcards {
    my ($class, $c, @vcards) = @_;

	my @geocode_lookup_threads;
    my $ret = {};
    my $ret_mutex;
	share($ret_mutex);
    share($ret);

    foreach my $vcard (@vcards) {
        # chill out for a minute if doing a lot of lookups
		my $thread_count = scalar(threads->list(threads::running)) || 0;
        threads->yield while $thread_count && $thread_count > 5;

        # do async geocode lookup
        my $thread = $class->geocode_async($c, $vcard, sub {
            my ($vcard, $loc_geocoded) = @_;
            
            if ($loc_geocoded && $loc_geocoded->latitude && $loc_geocoded->longitude) {
                my $geo_ret = {};
                share($geo_ret);

                $geo_ret->{lat} = $loc_geocoded->latitude;
                $geo_ret->{lon} = $loc_geocoded->longitude;

                lock $ret_mutex;
                $ret->{$vcard->id} = $geo_ret;
            }
        });

        push @geocode_lookup_threads, $thread if $thread;
    }

	# wait for lookups to finish
	$_->join foreach @geocode_lookup_threads;

    return $ret;
}

sub geocode_async {
    my ($class, $c, $vcard, $cb) = @_;

	my $gmaps_api_key = $class->googlemaps_api_key($c);
	my $gcoder = Geo::Coder::GoogleMaps->new(apikey => $gmaps_api_key, output => 'json');
    return undef unless $gcoder;

    my $loc;
    my $loc_cache_key = $class->cache_key($vcard);

    # check if this lookup is in the cache
    $loc = $c->cache->get($loc_cache_key);
    
    if ($loc) {
        if (ref $loc && $loc->latitude && $loc->longitude) {
            $cb->($vcard, $loc);
        }

        return;
    }

    $c->log->debug("Geocode cache miss for $loc_cache_key");

    my $thread = async {
        my $cache_mutex;
        share($cache_mutex);

        my $loc_string = $class->loc_string($vcard);
        $loc = $gcoder->geocode(location => $loc_string) if $loc_string;

        if (ref $loc && $loc->latitude && $loc->longitude) {
            {
                lock $cache_mutex;
                $c->cache->set($loc_cache_key, $loc);
            }
            $cb->($vcard, $loc);
        } else {
            # geocode lookup failed, but save empty cache entry so we don't try to look it up again
            lock $cache_mutex;
            $c->cache->set($loc_cache_key, "empty");
        }
    };

    return $thread;
}

1;
